/*
 * norx.cpp
 * This file is part of norxx
 *
 * Copyright (C) 2014 - Sebastian Gesemann
 *
 * norxx is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * norxx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with norxx. If not, see <http://www.gnu.org/licenses/>.
 */

#include "norx.hpp"

namespace norx {

/// adds message padding
void common::finish(array_ref<u8> buff)
{
	if (byte_index==rate_bytes) {
		// already "full", the message padding has to go into the
		// next block
		ds_and_F(R);
		byte_index = 0;
	}
	buff[byte_index  ] ^=    1;
	buff[rate_bytes-1] ^= 0x80;
	byte_index = rate_bytes; // rate block filled up with padding
}

/// prepares buffer for new data and returns the number of bytes
/// that can be accepted from the desired amount without another
/// permutation invocation.
unsigned common::moredata(std::size_t howmuch)
{
	assert(howmuch>0);
	if (byte_index == rate_bytes) {
		ds_and_F(R);
		byte_index = 0;
	}
	const auto max_chunk = rate_bytes - byte_index;
	return howmuch < max_chunk ? howmuch : max_chunk;
}

void common::feed(array_ref<u8> buffer,
                  domain_t what, array_ref<const u8> data)
{
	if (!data) return;
	assert(what == domain_header || what == domain_trailer);
	if (active_domain != what && active_domain != domain_zero) {
		finish(buffer);
	}
	active_domain = what;
	while (!!data) {
		auto chunk = moredata(data.size());
		auto* const blk = buffer.base() + byte_index;
		for (unsigned i=0; i<chunk; ++i) {
			blk[i] ^= data[i];
		}
		data.consume_first_n(chunk);
		byte_index += chunk;
	}
}

void common::encrypt(array_ref<u8> buffer,
                     array_ref<const u8> in,
                     array_ref<u8> out)
{
	assert(in.size() == out.size());
	if (!in) return;
	if (active_domain != domain_payload && active_domain != domain_zero) {
		finish(buffer);
	}
	active_domain = domain_payload;
	while (!!in) {
		auto chunk = moredata(in.size());
		auto* const blk = buffer.base() + byte_index;
		for (unsigned i=0; i<chunk; ++i) {
			out[i] = (blk[i] ^= in[i]);
		}
		in .consume_first_n(chunk);
		out.consume_first_n(chunk);
		byte_index += chunk;
	}
}

void common::decrypt(array_ref<u8> buffer,
                     array_ref<const u8> in,
                     array_ref<u8> out)
{
	assert(in.size() == out.size());
	if (!in) return;
	if (active_domain != domain_payload && active_domain != domain_zero) {
		finish(buffer);
	}
	active_domain = domain_payload;
	while (!!in) {
		auto chunk = moredata(in.size());
		auto* const blk = buffer.base() + byte_index;
		for (unsigned i=0; i<chunk; ++i) {
			out[i] = blk[i] ^ in[i];
			blk[i] = in[i];
		}
		in .consume_first_n(chunk);
		out.consume_first_n(chunk);
		byte_index += chunk;
	}
}

void common::tag_generate(array_ref<u8> buffer, array_ref<u8> out)
{
	if (active_domain != domain_zero) {
		finish(buffer);
	}
	active_domain = domain_tag;
	ds_and_F(R*2);
	copy(crop(buffer,0,Abytes),out);
	byte_index = byte_index;
	active_domain = domain_zero;
}

bool common::tag_check(array_ref<u8> buffer, array_ref<const u8> in)
{
	if (active_domain != domain_zero) {
		finish(buffer);
	}
	active_domain = domain_tag;
	ds_and_F(R*2);
	byte_index = byte_index;
	active_domain = domain_zero;
	return match(crop(buffer,0,Abytes),in);
}

} // namespace norx

